package dkbta.ontology.elements.repeatingpattern;

import java.util.LinkedList;
import java.util.List;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.Element;
import dkbta.ontology.elements.ElementDef;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.pattern.conditions.DurationCondition;
import dkbta.ontology.loading.OntologyException;
import dkbta.ontology.values.DurationValues;
import dkbta.ontology.values.ValueCondition;
import dkbta.processing.ActionTimer;
import dkbta.processing.Patient;
import dkbta.util.time.TimeInterval;

public class RepeatingPatternDef extends ElementDef{
	/** The name of the ontology element */
	private final String _ontologyElementName;

	/** The type of the ontology element */
	private final ElementType _ontologyElementType;

	/** The value condition on the ontology element */
	private final ValueCondition _valueCondition;

	/** The duration condition on the ontology element */
	private final DurationCondition _durationCondition;

	/** The number of elements that should be in the pattern */
	private final int _cardinality;

	/** The valid lengths of the gap between subsequent instances of the element */
	private final DurationValues _validGapDuration;

	/**
	 * Initializes a repeating pattern definition
	 * 
	 * @param name The element's name
	 * @param componentType The element's component type
	 * @param ontologyElementName The name of the corresponding ontology element
	 * @param ontologyElementType The type of the corresponding ontology element
	 * @param valueCondition The value condition on the ontology element
	 * @param durationCondition The duration condition on the ontology element
	 * @param cardinality The number of elements that should be in the pattern
	 * @param validGapDuration The valid lengths of the gap between subsequent instances
	 *        of the element
	 */
	public RepeatingPatternDef(String name, String componentType,
		String ontologyElementName, ElementType ontologyElementType,
		ValueCondition valueCondition, DurationCondition durationCondition,
		int cardinality, DurationValues validGapDuration){
		super(ElementType.REPEATINGPATTERN, name, componentType, null);
		_ontologyElementName = ontologyElementName;
		_ontologyElementType = ontologyElementType;
		_valueCondition = valueCondition;
		_durationCondition = durationCondition;
		_cardinality = cardinality;
		_validGapDuration = validGapDuration;
	}

	@Override
	protected void compute(Patient p) throws UnsupportedOperationException{
		ActionTimer actionTimer = ActionTimer.getInstance();
		int patternActionId = actionTimer.actionStart();

		List<RepeatingPattern> patterns = p.getRepeatingPatterns(_name);

		List<? extends Element> elements = p.getElements(_ontologyElementType,
			_ontologyElementName);
		final int size = elements.size();
		
		long patternStartTime = -1;
		long lastElementEndTime = -1;
		LinkedList<Element> patternElements = new LinkedList<Element>();
		for (int i = 0; i < size; ++i){
			Element e = elements.get(i);
			
			if (matchesConditions(e)){
				// The current element can be added to the pattern
				TimeInterval ti = e.getTimeInterval();
				long startTime = ti.getStartTime();
				if (lastElementEndTime >= 0
						&& _validGapDuration.isValid(startTime - lastElementEndTime)){
					// There is a previous element and the gap is valid
					lastElementEndTime = ti.getEndTime();
				}else{
					// Either there is no previous element or the gap between the new
					// element and the previous one is invalid. In such a case the current
					// element starts a new pattern
					patternStartTime = startTime;
					lastElementEndTime = ti.getEndTime();
					patternElements.clear();		
				}
				patternElements.add(e);

				// Checking if we have enough elements for the pattern
				if (patternElements.size() == _cardinality){
					// Creating the pattern from the current elements
					RepeatingPattern rp = new RepeatingPattern(_name, patternStartTime,
							lastElementEndTime, patternElements
									.toArray(new Element[patternElements.size()]));
					patterns.add(rp);
					
					// Discarding the first element
					patternElements.removeFirst();
										
					// Setting the new pattern to start at the new first element
					Element first = patternElements.getFirst();
					if (first == null){
						// The cardinality was 1, no further elements
						lastElementEndTime = -1;
					}else{
						patternStartTime = first.getTimeInterval().getStartTime();
					}
				}
			}else{
				// The current element doesn't match and breaks the pattern
				lastElementEndTime = -1;
				patternElements.clear();				
			}
		}

		actionTimer.actionEnd(patternActionId, p.getPatientId(), _name, _type,
			ActionTimer.Action.REPEATING_PATTERN_COMPUTATION);
	}

	@Override
	public RepeatingPattern newInstance(long startTime, long endTime, String[] values){
		return new RepeatingPattern(_name, startTime, endTime, null);
	}

	@Override
	public void validate(Ontology ontology) throws OntologyException{
		if (_valueCondition != null){
			_valueCondition.validate(ontology);
		}
		ElementDef ed = ontology.getElement(_ontologyElementType, _ontologyElementName);
		if (ed == null){
			throw new OntologyException("The pattern refers to a non-existing "
					+ _ontologyElementType + ": " + _ontologyElementName);
		}
		_prerequisiteElements.add(ed);
	}

	private boolean matchesConditions(Element e){
		if (_durationCondition != null && !_durationCondition.checkCondition(e)){
			return false;
		}
		if (_valueCondition != null && !_valueCondition.checkCondition(e)){
			return false;
		}
		return true;
	}

	@Override
	public String completeToString(){
		StringBuilder sb = new StringBuilder(super.completeToString());
		sb.append("Ontology Element: ").append(_ontologyElementName);
		sb.append("\r\nValue Condition: ").append(_valueCondition);
		sb.append("\r\nDuration Condition: ").append(_durationCondition);
		sb.append("\r\nCardinality: ").append(_cardinality);
		sb.append("\r\nValid Gaps: ").append(_validGapDuration);
		sb.append("\r\n");
		return sb.toString();
	}
}
